package com.utooo.bid.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.utooo.bid.config.RedisChatMemoryStore;
import com.utooo.bid.entity.BidCatalog;
import com.utooo.bid.entity.BidCatalogGeneration;
import com.utooo.bid.entity.BidDocument;
import com.utooo.bid.entity.BidDocumentAnalysis;
import com.utooo.bid.entity.BidIndicator;
import com.utooo.bid.entity.BidParsedContent;
import com.utooo.bid.entity.BidContentGeneration;
import com.utooo.bid.entity.CatalogContent;
import com.utooo.bid.enums.FileType;
import com.utooo.bid.mapper.BidDocumentAnalysisMapper;
import com.utooo.bid.mapper.BidDocumentMapper;
import com.utooo.bid.mapper.BidIndicatorMapper;
import com.utooo.bid.mapper.BidContentGenerationMapper;
import com.utooo.bid.mapper.BidParsedContentMapper;
import com.utooo.bid.model.CatalogVO;
import com.utooo.bid.model.LlmCatalogResp;
import com.utooo.bid.prompt.BidPromptTemplate;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.UrlDocumentLoader;
import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.StreamingResponseHandler;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.output.Response;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.utooo.bid.prompt.BidPromptTemplate.BID_BUSINESS_CATALOG_CONTENT_PROMPT_BASED_ON_KNOWLEDGE_TEMPLATE;
import static com.utooo.bid.prompt.BidPromptTemplate.BID_BUSINESS_CATALOG_CONTENT_PROMPT_TEMPLATE;
import static com.utooo.bid.prompt.BidPromptTemplate.BID_TECH_CATALOG_CONTENT_PROMPT_BASED_ON_KNOWLEDGE_TEMPLATE;
import static com.utooo.bid.prompt.BidPromptTemplate.BID_TECH_CATALOG_CONTENT_PROMPT_TEMPLATE;

@Service
@Slf4j
@RequiredArgsConstructor
public class BidDocumentServiceImpl extends ServiceImpl<BidDocumentMapper, BidDocument> implements BidDocumentService {

    private final Assistant assistant;
    private final RagAssistant ragAssistant;

    private final BidIndicatorMapper bidIndicatorMapper;
    private final BidParsedContentMapper bidParsedContentMapper;
    private final BidDocumentAnalysisMapper bidDocumentAnalysisMapper;
    private final BidCatalogService bidCatalogService;
    private final BidCatalogGenerationService bidCatalogGenerationService;
    private final RedisChatMemoryStore redisChatMemoryStore;
    private final StreamingChatLanguageModel streamingChatLanguageModel;
    private final CatalogContentService catalogContentService;
    private final BidContentGenerationMapper bidContentGenerationMapper;
    private final ThreadPoolTaskExecutor bidDocumentProcessorExecutor;
    private final ThreadPoolTaskExecutor bidDocumentCatalogExecutor;

    private final CompanyService companyService;
    private final CompanyQualificationService companyQualificationService;
    private final CompanyPerformanceService companyPerformanceService;
    private final CompanyFinancialService companyFinancialService;

    @Value("${bid.main.server-domain:}")
    private String mainServerDomain;


    @Override
    public BidDocument processBidDocument(String bidId) {
        BidDocument bidDocument = this.getById(bidId);
        if (bidDocument == null) {
            throw new RuntimeException("未找到招标文件记录");
        }
        String fileListStr = bidDocument.getFileListStr();
        if (StrUtil.isBlank(fileListStr)) {
            throw new RuntimeException("未上传投标文件");
        }
        List<BidDocument.FileInfo> fileInfos = JSON.parseArray(fileListStr, BidDocument.FileInfo.class);
        if (CollUtil.isEmpty(fileInfos)) {
            throw new RuntimeException("未上传投标文件");
        }
        // 创建解析记录
        BidDocumentAnalysis preBidDocumentAnalysis = BidDocumentAnalysis.builder()
                .bidDocumentId(bidId)
                .analysisTime(LocalDateTime.now())
                .build();
        bidDocumentAnalysisMapper.insert(preBidDocumentAnalysis);
        log.info("文档id：{}，解析记录id: {}", bidId, preBidDocumentAnalysis.getId());


        bidDocumentProcessorExecutor.execute(() -> {
            try {
                // 提取文本
                Document document = UrlDocumentLoader.load(mainServerDomain + fileInfos.get(0).getUrl(),
                        new ApacheTikaDocumentParser());
                String extractedText = document.text();
//            bidDocument.setOriginalContent(extractedText);

                List<BidIndicator> bidIndicators = bidIndicatorMapper
                        .selectList(new LambdaQueryWrapper<BidIndicator>()
                                .orderByAsc(BidIndicator::getSort));
                Map<String, String> bidIndicatorMap = bidIndicators.stream()
                        .collect(Collectors.toMap(BidIndicator::getName, BidIndicator::getId,
                                (k1, k2) -> k1, LinkedHashMap::new));
                String bidIndicatorNameStr = StrUtil.join(";\n", bidIndicators
                        .stream()
                        .peek(i -> {
                            i.setName("-" + i.getName());
                        }).map(BidIndicator::getName)
                        .collect(Collectors.toList()));
                // 创建提示词
                String prompt = BidPromptTemplate.createBidAnalysisPrompt(bidIndicatorNameStr, extractedText);
                log.info("生成的提示词长度: {}", prompt.length());

                String conversationId = UUID.randomUUID().toString();
                String generatedContent = assistant.chat(conversationId, prompt);
//                String generatedContent = "";
                log.info("会话id：{}", conversationId);
                log.info("解析出的内容: {}", generatedContent);
                preBidDocumentAnalysis.setSessionId(conversationId);
                if (StrUtil.isNotBlank(generatedContent)) {
                    JSONObject object = JSONUtil.parseObj(generatedContent);
                    bidIndicatorMap.forEach((k, v) -> {
                        String content = object.getStr(k);
                        bidParsedContentMapper.insert(BidParsedContent
                                .builder()
                                .bidDocumentId(bidDocument.getId())
                                .bidCatalogId(v)
                                .content(content)
                                .build());
                    });
                    String bidDocumentAnalysisResp = assistant.extractBidAnalysisRecord(conversationId);
                    log.info("指定的指标内容: {}", bidDocumentAnalysisResp);
                    if (StrUtil.isNotBlank(bidDocumentAnalysisResp)) {
                        BidDocumentAnalysis bidDocumentAnalysis = JSONUtil.toBean(bidDocumentAnalysisResp, BidDocumentAnalysis.class);
                        if (bidDocumentAnalysis != null) {
                            bidDocumentAnalysis.setId(preBidDocumentAnalysis.getId());
                            bidDocumentAnalysis.setBidDocumentId(preBidDocumentAnalysis.getBidDocumentId());
                            bidDocumentAnalysis.setAnalysisTime(preBidDocumentAnalysis.getAnalysisTime());
                            bidDocumentAnalysis.setSessionId(conversationId);
                            bidDocumentAnalysisMapper.updateById(bidDocumentAnalysis);
                        } else {
                            bidDocumentAnalysisMapper.updateById(preBidDocumentAnalysis);
                        }
                    } else {
                        bidDocumentAnalysisMapper.updateById(preBidDocumentAnalysis);
                    }
                }
                bidDocument.setStatus(2);
                log.info("处理标书文件成功");
            } catch (Exception e) {
                bidDocument.setStatus(3);
                log.error("处理标书文件失败", e);
            } finally {
                this.updateById(bidDocument);
            }
        });
        return bidDocument;
    }

    public boolean isValidFileType(MultipartFile file) {
        String fileName = file.getOriginalFilename();
        if (fileName == null) {
            return false;
        }
        String extension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
        return Arrays.stream(FileType.values())
                .map(FileType::getExtension)
                .anyMatch(ext -> ext.equals(extension));
    }

    @Override
    public void generateBidCatalog(String bidId, int type) {
        List<CatalogVO> catalogList = new ArrayList<>();
        BidDocumentAnalysis bidDocumentAnalysis = getBidDocumentAnalysis(bidId);
        BidCatalogGeneration bidCatalogGeneration = BidCatalogGeneration.builder()
                .bidId(bidId)
                .catalogType(type)
                .status(1)
                .build();
        bidCatalogGenerationService.save(bidCatalogGeneration);

        String sessionId = bidDocumentAnalysis.getSessionId();
        bidDocumentCatalogExecutor.execute(() -> {
            String resp = doInvoker(type, bidCatalogGeneration, sessionId);

            List<LlmCatalogResp> llmCatalogList;
            try {
                llmCatalogList = JSON.parseArray(resp, LlmCatalogResp.class);
            } catch (Exception e) {
                bidCatalogGeneration.setStatus(3);
                bidCatalogGenerationService.updateById(bidCatalogGeneration);
                throw new RuntimeException(e);
            }
            if (CollUtil.isEmpty(llmCatalogList)) {
                log.error("标书的目录为空");
            }
            List<BidCatalog> bidCatalogs = new ArrayList<>();
            for (int i = 0; i < llmCatalogList.size(); i++) {
                addCatalog(llmCatalogList.get(i), "0", i, type, bidId, catalogList, bidCatalogs);
            }
            bidCatalogService.saveBatch(bidCatalogs);
            bidCatalogGeneration.setStatus(2);
            bidCatalogGeneration.setGenerateTime(LocalDateTime.now());
            bidCatalogGenerationService.updateById(bidCatalogGeneration);
        });
    }

    private String doInvoker(int type, BidCatalogGeneration bidCatalogGeneration, String sessionId) {
        String resp;
        try {
            if (type == 1) {
                resp = assistant.generateBusiCatalog(sessionId);
                log.info("标书的商务标目录: {}", resp);
            } else {
                resp = assistant.generateTechCatalog(sessionId);
                log.info("标书的技术标目录: {}", resp);
            }
        } catch (Exception e) {
            bidCatalogGeneration.setStatus(3);
            bidCatalogGenerationService.updateById(bidCatalogGeneration);
            throw new RuntimeException(e);
        }
        if (StrUtil.isBlank(resp)) {
            bidCatalogGeneration.setStatus(3);
            bidCatalogGenerationService.updateById(bidCatalogGeneration);
            log.error("标书的目录为空");
        }
        if (resp.startsWith("```json") && resp.endsWith("```")) {
            resp = resp.replaceAll("^```json\\s*|\\s*```$", "").trim();
        }
        return resp;
    }

    @NotNull
    private BidDocumentAnalysis getBidDocumentAnalysis(String bidId) {
        BidDocumentAnalysis bidDocumentAnalysis = bidDocumentAnalysisMapper
                .selectOne(new LambdaQueryWrapper<BidDocumentAnalysis>()
                        .eq(BidDocumentAnalysis::getBidDocumentId, bidId));
        if (bidDocumentAnalysis == null) {
            log.error("历史招标文件解析记录为空");
            throw new IllegalArgumentException("历史招标文件解析记录为空");
        }
        BidDocument bidDocument = this.getById(bidId);
        if (bidDocument == null) {
            throw new IllegalArgumentException("招标文件不存在");
        }
        if (bidDocument.getStatus() != 2) {
            throw new IllegalArgumentException("招标文件正在解析中");
        }
        return bidDocumentAnalysis;
    }

    @Override
    public List<CatalogVO> getCatalogTree(String bidId, int type) {
        BidCatalogGeneration catalogGeneration = bidCatalogGenerationService.getOne(new LambdaQueryWrapper<BidCatalogGeneration>()
                .eq(BidCatalogGeneration::getBidId, bidId)
                .eq(BidCatalogGeneration::getCatalogType, type));
        if (catalogGeneration == null) {
            throw new IllegalArgumentException("目录生成记录不存在");
        }
        if (catalogGeneration.getStatus() != 2) {
            throw new IllegalArgumentException("目录正在生成中");
        }
        List<CatalogVO> catalogList = new ArrayList<>();
        List<BidCatalog> bidCatalogs = bidCatalogService.list(new LambdaQueryWrapper<BidCatalog>()
                .eq(BidCatalog::getBidId, bidId)
                .eq(BidCatalog::getCatalogType, type));
        if (CollUtil.isEmpty(bidCatalogs)) {
            return catalogList;
        }
        return convertBidCatalogsToTree(bidCatalogs);
    }

    @Override
    public void updateBidCatalog(String catalogId, String name, String parentId) {
        if (StrUtil.isBlank(catalogId) && StrUtil.isBlank(parentId)) {
            throw new IllegalArgumentException("参数有误");
        }
        if (StrUtil.isNotBlank(catalogId)) {
            BidCatalog bidCatalog = bidCatalogService.getById(catalogId);
            bidCatalog.setCatalogName(name);
            bidCatalogService.updateById(bidCatalog);
            return;
        }

        if (StrUtil.isNotBlank(parentId)) {
            BidCatalog bidCatalog = bidCatalogService.getById(parentId);
            if (!"0".equals(parentId) && bidCatalog == null) {
                throw new IllegalArgumentException("父目录不存在");
            }
            // 根据父分类获取子分类sort的最大值
            List<BidCatalog> bidCatalogs = bidCatalogService.list(new LambdaQueryWrapper<BidCatalog>()
                    .eq(BidCatalog::getParentId, parentId));
            int maxSort = getMaxSortByParentId(bidCatalogs);
            bidCatalogService.save(new BidCatalog(
                    IdUtil.getSnowflake().nextIdStr(),
                    name,
                    bidCatalog.getCatalogType(),
                    bidCatalog.getBidId(),
                    maxSort + 1,
                    parentId
            ));
        }
    }

    @Override
    public void deleteBidCatalog(String catalogId) {
        BidCatalog bidCatalog = bidCatalogService.getById(catalogId);
        if (bidCatalog == null) {
            throw new IllegalArgumentException("目录不存在");
        }
        List<String> idsToDelete = new ArrayList<>();
        idsToDelete.add(catalogId);
        List<BidCatalog> bidCatalogs = bidCatalogService.list(
                new LambdaQueryWrapper<BidCatalog>().eq(BidCatalog::getBidId, bidCatalog.getBidId())
                        .eq(BidCatalog::getCatalogType, bidCatalog.getCatalogType()));
        collectIdsToDelete(bidCatalogs, catalogId, idsToDelete);
        if (CollUtil.isNotEmpty(idsToDelete)) {
            bidCatalogService.removeByIds(idsToDelete);
        }
    }

    @Override
    public SseEmitter genContentByBidCatalog(String bidId, int type, int useKnowledge, String companyId) {
        BidDocumentAnalysis bidDocumentAnalysis = getBidDocumentAnalysis(bidId);
        List<BidCatalogGeneration> catalogGenerations = bidCatalogGenerationService.list(
                new LambdaQueryWrapper<BidCatalogGeneration>()
                        .eq(BidCatalogGeneration::getBidId, bidId)
                        .eq(BidCatalogGeneration::getCatalogType, type)
                        .eq(BidCatalogGeneration::getStatus, 2)
                        .orderByDesc(BidCatalogGeneration::getGenerateTime)
                        .last("limit 1"));
        if (CollUtil.isEmpty(catalogGenerations)) {
            throw new IllegalArgumentException("目录生成记录不存在");
        }
        BidCatalogGeneration catalogGeneration = catalogGenerations.get(0);
        if (catalogGeneration.getStatus() != 2) {
            throw new IllegalArgumentException("目录正在生成中");
        }

        String sessionId = bidDocumentAnalysis.getSessionId();
        List<ChatMessage> history = redisChatMemoryStore.getMessages(sessionId);
        if (CollUtil.isEmpty(history)) {
            throw new IllegalArgumentException("会话已过期");
        }
        CatalogContent catalogContent = CatalogContent.builder()
                .bidDocumentId(bidDocumentAnalysis.getBidDocumentId())
                .catalogType(type)
                .status(1)
                .build();
        catalogContentService.save(catalogContent);

        BidContentGeneration bidContentGeneration = BidContentGeneration
                .builder()
                .makeStatus(1)
                .bidDocumentId(bidId)
                .projectName(bidDocumentAnalysis.getBidProjectName())
                .projectCode(bidDocumentAnalysis.getBidProjectCode())
                .openBidTime(bidDocumentAnalysis.getBidOpeningTime())
                .projectBudget(bidDocumentAnalysis.getBidProjectBudget())
                .biddingCompanyName(bidDocumentAnalysis.getBidCompanyName())
                .contactPerson(bidDocumentAnalysis.getContactPerson())
                .contactPhone(bidDocumentAnalysis.getContactPhone())
                .bidderId(companyId)
                .build();
        bidContentGenerationMapper.insert(bidContentGeneration);

        // 使用知识库生成
        if (useKnowledge == 1) {
            return getSseEmitter(catalogContent, type, bidContentGeneration);
        }

        UserMessage userMessage;
        if (type == 1) {
            userMessage = UserMessage.from(BID_BUSINESS_CATALOG_CONTENT_PROMPT_TEMPLATE);
        } else {
            userMessage = UserMessage.from(BID_TECH_CATALOG_CONTENT_PROMPT_TEMPLATE);
        }
        history.add(userMessage);
        return getSseEmitter(history, sessionId, catalogContent, bidContentGeneration);
    }

    @Override
    public SseEmitter genBidContent(String bidId) {
        return null;
    }

    @Override
    public Flux<String> genBidContentReactive(String bidId, int type, int useKnowledge, String companyId) {
        BidDocumentAnalysis bidDocumentAnalysis = getBidDocumentAnalysis(bidId);
        BidCatalogGeneration catalogGeneration = bidCatalogGenerationService.getOne(
                new LambdaQueryWrapper<BidCatalogGeneration>()
                        .eq(BidCatalogGeneration::getBidId, bidId)
                        .eq(BidCatalogGeneration::getCatalogType, type)
                        .eq(BidCatalogGeneration::getStatus, 2)
                        .orderByDesc(BidCatalogGeneration::getGenerateTime)
                        .last("limit 1"));
        if (catalogGeneration == null) {
            throw new IllegalArgumentException("目录生成记录不存在");
        }
        if (catalogGeneration.getStatus() != 2) {
            throw new IllegalArgumentException("目录正在生成中");
        }

        String sessionId = bidDocumentAnalysis.getSessionId();
        List<ChatMessage> history = redisChatMemoryStore.getMessages(sessionId);
        if (CollUtil.isEmpty(history)) {
            throw new IllegalArgumentException("会话已过期");
        }
        CatalogContent catalogContent = CatalogContent.builder()
                .bidDocumentId(bidDocumentAnalysis.getBidDocumentId())
                .catalogType(type)
                .status(1)
                .build();
        catalogContentService.save(catalogContent);

        BidContentGeneration bidContentGeneration = BidContentGeneration
                .builder()
                .makeStatus(1)
                .bidDocumentId(bidId)
                .projectName(bidDocumentAnalysis.getBidProjectName())
                .projectCode(bidDocumentAnalysis.getBidProjectCode())
                .openBidTime(bidDocumentAnalysis.getBidOpeningTime())
                .projectBudget(bidDocumentAnalysis.getBidProjectBudget())
                .biddingCompanyName(bidDocumentAnalysis.getBidCompanyName())
                .contactPerson(bidDocumentAnalysis.getContactPerson())
                .contactPhone(bidDocumentAnalysis.getContactPhone())
                .bidderId(companyId)
                .build();
        bidContentGenerationMapper.insert(bidContentGeneration);

        UserMessage userMessage;
        if (type == 1) {
            userMessage = UserMessage.from(BID_BUSINESS_CATALOG_CONTENT_PROMPT_TEMPLATE);
        } else {
            userMessage = UserMessage.from(BID_TECH_CATALOG_CONTENT_PROMPT_TEMPLATE);
        }
        history.add(userMessage);
        return Flux.create(sink -> {
            streamingChatLanguageModel.chat(history, new StreamingChatResponseHandler() {
                @Override
                public void onPartialResponse(String partialResponse) {
                    log.info("onPartialResponse:{}", partialResponse);
                    sink.next(partialResponse);
                }

                @Override
                public void onCompleteResponse(ChatResponse completeResponse) {
                    log.info("complete:{}", completeResponse);
                    sink.complete();
                }

                @Override
                public void onError(Throwable error) {
                    sink.error(error);
                }
            });
        });
    }

    @NotNull
    private SseEmitter getSseEmitter(List<ChatMessage> history, String sessionId,
                                     CatalogContent catalogContent,
                                     BidContentGeneration bidContentGeneration) {
        // 无超时
        SseEmitter emitter = new SseEmitter(-1L);
        // 定义心跳任务
/*        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            try {
                emitter.send(SseEmitter.event().comment("keep-alive"));
            } catch (IOException e) {
                scheduler.shutdown();
            }
        }, 0, 15, TimeUnit.SECONDS);*/ // 每 15 秒发送一次心跳
        // 创建响应处理器
        AtomicReference<StringBuilder> responseBuilder = new AtomicReference<>(new StringBuilder());
        StreamingChatResponseHandler handler = new StreamingChatResponseHandler() {

            @Override
            public void onPartialResponse(String s) {
                try {
                    responseBuilder.get().append(s);
                    emitter.send(SseEmitter.event().name("token").data(s));
                } catch (IOException e) {
                    onError(e);
                }
            }

            @Override
            public void onCompleteResponse(ChatResponse chatResponse) {
                try {
                    String content = responseBuilder.get().toString();
                    history.add(AiMessage.from(content));
                    redisChatMemoryStore.updateMessages(sessionId, history);
                    catalogContent.setStatus(2);
                    catalogContent.setGenerateTime(LocalDateTime.now());
                    catalogContent.setContent(content);
                    catalogContentService.updateById(catalogContent);
                    emitter.send(SseEmitter.event().name("complete").data(content));
                    emitter.complete();
//                    scheduler.shutdown();
                    // 判断标书是否已经完整生成：技术标与商务标内容均生成
                    updateContentGenStatus(catalogContent.getBidDocumentId(), bidContentGeneration);
                } catch (IOException e) {
                    onError(e);
                }
            }

            @Override
            public void onError(Throwable throwable) {
                emitter.completeWithError(throwable);
            }
        };
        ChatRequest chatRequest = ChatRequest.builder().messages(history).build();
        // 发送聊天消息并处理流式响应
        CompletableFuture.runAsync(() -> {
            try {
                streamingChatLanguageModel.chat(chatRequest, handler);
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        // 设置异常处理
        emitter.onTimeout(emitter::complete);
        emitter.onError(throwable -> {
            throwable.printStackTrace();
            emitter.complete();
        });
        return emitter;
    }

    private SseEmitter getSseEmitter(CatalogContent catalogContent, int type, BidContentGeneration bidContentGeneration) {
        // 无超时
        SseEmitter emitter = new SseEmitter(-1L);
        // 定义心跳任务
/*        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            try {
                emitter.send(SseEmitter.event().comment("keep-alive"));
            } catch (IOException e) {
                scheduler.shutdown();
            }
        }, 0, 15, TimeUnit.SECONDS);*/ // 每 15 秒发送一次心跳
        Flux<String> flux;
        if (type == 1) {
            flux = ragAssistant.stream(BID_BUSINESS_CATALOG_CONTENT_PROMPT_BASED_ON_KNOWLEDGE_TEMPLATE);
        } else {
            flux = ragAssistant.stream(BID_TECH_CATALOG_CONTENT_PROMPT_BASED_ON_KNOWLEDGE_TEMPLATE);
        }
        StringBuilder fullResponse = new StringBuilder();
        flux.subscribe(
                chunk -> {
                    try {
                        emitter.send(SseEmitter.event().data(chunk));
                        fullResponse.append(chunk);
                    } catch (IOException e) {
                        log.error("Failed to send SSE event", e);
                        emitter.completeWithError(e);
                    }
                },
                error -> {
                    log.error("Streaming error for question: {}", error.getMessage());
                    emitter.completeWithError(error);
                },
                () -> {
                    try {
                        // 发送完成信号
                        String completeResponse = fullResponse.toString();
                        catalogContent.setStatus(2);
                        catalogContent.setGenerateTime(LocalDateTime.now());
                        catalogContent.setContent(completeResponse);
                        catalogContentService.updateById(catalogContent);
                        emitter.send(SseEmitter.event()
                                .name("complete")
                                .data(completeResponse));
//                        scheduler.shutdown();
                        // 判断标书是否已经完整生成：技术标与商务标内容均生成
                        updateContentGenStatus(catalogContent.getBidDocumentId(), bidContentGeneration);
                    } catch (IOException e) {
                        log.error("Failed to send completion event", e);
                        emitter.completeWithError(e);
                    } finally {
                        emitter.complete();
                    }
                }
        );
        return emitter;
    }

    private void updateContentGenStatus(String bidDocumentId, BidContentGeneration bidContentGeneration) {
        List<CatalogContent> catalogContents = catalogContentService
                .list(new LambdaQueryWrapper<CatalogContent>()
                        .eq(CatalogContent::getBidDocumentId, bidDocumentId));
        if (CollUtil.isEmpty(catalogContents)) {
            return;
        }
        Map<Integer, Integer> statusMap = catalogContents
                .stream()
                .collect(Collectors.toMap(CatalogContent::getCatalogType,
                        CatalogContent::getStatus,
                        (existing, replacement) -> existing));
        if (statusMap.size() != 2) {
            return;
        }
        if (statusMap.get(1) == null || statusMap.get(2) == null) {
            return;
        }
        if (statusMap.get(1) != 2 || statusMap.get(2) != 2) {
            return;
        }
        bidContentGeneration.setMakeStatus(2);
        bidContentGenerationMapper.updateById(bidContentGeneration);
    }

    private void collectIdsToDelete(List<BidCatalog> bidCatalogs, String parentId, List<String> idsToDelete) {
        for (BidCatalog bidCatalog : bidCatalogs) {
            if (parentId.equals(bidCatalog.getParentId())) {
                idsToDelete.add(bidCatalog.getId());
                collectIdsToDelete(bidCatalogs, bidCatalog.getId(), idsToDelete);
            }
        }
    }

    public int getMaxSortByParentId(List<BidCatalog> bidCatalogs) {
        // 使用Optional来处理可能的空值情况，并初始化最大值为-1
        Optional<Integer> maxSort = bidCatalogs.stream()
                .map(BidCatalog::getSort)
                .max(Integer::compare);
        return maxSort.orElse(-1);
    }

    public List<CatalogVO> convertBidCatalogsToTree(List<BidCatalog> bidCatalogs) {
        // 使用Map来存储所有CatalogVO以便于快速查找和建立父子关系
        Map<String, CatalogVO> catalogMap = new LinkedHashMap<>(bidCatalogs.size());

        for (BidCatalog bidCatalog : bidCatalogs) {
            CatalogVO catalogVO = new CatalogVO(
                    bidCatalog.getId(),
                    bidCatalog.getCatalogName(),
                    bidCatalog.getParentId()
            );
            catalogMap.put(bidCatalog.getId(), catalogVO);
        }

        // 建立父子关系
        List<CatalogVO> catalogs = new ArrayList<>();
        for (CatalogVO catalogVO : catalogMap.values()) {
            if ("0".equals(catalogVO.getParentId())) {
                catalogs.add(catalogVO);
            } else {
                CatalogVO parentCatalogVO = catalogMap.get(catalogVO.getParentId());
                if (parentCatalogVO != null) {
                    if (parentCatalogVO.getChildren() == null) {
                        parentCatalogVO.setChildren(new ArrayList<>());
                    }
                    parentCatalogVO.getChildren().add(catalogVO);
                }
            }
        }
        return catalogs;
    }

    public static void main(String[] args) {
        String str1 = "3.1 招标要求响应";
        String str2 = "3 招标要求响应";
        String str3 = "招标要求响应";
        System.out.println(startWithNumber(str1));
        System.out.println(startWithNumber(str2));
        System.out.println(startWithNumber(str3));
    }

    private static boolean startWithNumber(String str) {
        String regex = "^\\d+(\\.\\d+)?\\s.*";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);
        return matcher.matches();
    }

    private void addCatalog(LlmCatalogResp item, String parentId, int sort, int type, String
            bidId, List<CatalogVO> catalogList, List<BidCatalog> bidCatalogs) {
        String id = IdUtil.getSnowflake().nextIdStr();
        String catalogName = item.getCatalogName();
        String name = startWithNumber(catalogName)
                ? catalogName
                : (StrUtil.isNotBlank(item.getSectionNumber()) ?
                item.getSectionNumber() + catalogName : catalogName);

        CatalogVO catalogVO = new CatalogVO(id, name, parentId);
        BidCatalog bidCatalog = new BidCatalog(id, name, type, bidId, sort, parentId);
        bidCatalogs.add(bidCatalog);

        // 处理子节点
        if (CollUtil.isNotEmpty(item.getChildren())) {
            List<CatalogVO> childCatalogList = new ArrayList<>();
            for (int i = 0; i < item.getChildren().size(); i++) {
                LlmCatalogResp child = item.getChildren().get(i);
                addCatalog(child, id, i, type, bidId, childCatalogList, bidCatalogs);
            }
            catalogVO.setChildren(childCatalogList);
        }
        catalogList.add(catalogVO);

    }
}